﻿using Inaction;
using Inaction.ServiceContract;
using Inaction.Core;
using Inaction.Criteria;
using Inaction.Linq;
using Inaction.Mapping;
using Inaction.Util;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Linq.Expressions;

namespace Inaction
{
    public abstract partial class Entity<T>
    {
        #region Query
        public static List<T> Query()
        {
            return Query((CriteriaBase)null);
        }

        public static List<T> Query(CriteriaBase criteria)
        {
            var sql = EntitySqlStatement.GetSelectStatement<T>(criteria);
            return Query(sql);
        }

        public static List<T> Query(IDbTransaction trans)
        {
            return Query(trans, (CriteriaBase)null);
        }

        public static List<T> Query(IDbTransaction trans, CriteriaBase criteria)
        {
            var sql = EntitySqlStatement.GetSelectStatement<T>(criteria);
            return Query(sql, trans);
        }

        public static List<T> Query(Expression<Func<T, bool>> e)
        {
            return Query(Linq.LinqHelper<T>.CreateSelectSql(e));
        }

        public static List<T> Query(IDbTransaction trans, Expression<Func<T, bool>> e)
        {
            var sql = Linq.LinqHelper<T>.CreateSelectSql(e);
            return Query(sql, trans);
        }

        public static T Query(Guid pkValue)
        {
            var sql = EntitySqlStatement.GetSelectStatementWithPropertyPara<T>(null, new ParaValue { Name = ClassInfo.PrimaryKey.Name, Value = pkValue });
            return RemotingMethod.Invoke<T>(new Func<SqlStatement, T>(DoSingle), sql);
        }

        public static T Query(IDbTransaction trans, Guid pkValue)
        {
            var sql = EntitySqlStatement.GetSelectStatementWithPropertyPara<T>(null, new ParaValue { Name = ClassInfo.PrimaryKey.Name, Value = pkValue });
            return DoSingle(sql, trans);
        }
        #endregion

        #region ExecuteScalarSql
        public static T Single(Expression<Func<T, bool>> e)
        {
            var sql = LinqHelper<T>.CreateSelectSql(e);
            Func<SqlStatement, T> func = DoSingle;
            return RemotingMethod.Invoke<T>(func, sql);
        }

        public static T Single(IDbTransaction trans, Expression<Func<T, bool>> e)
        {
            var sql = LinqHelper<T>.CreateSelectSql(e);
            return DoSingle(sql, trans);
        }

        public static int Count(Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(e, DbSchame.SqlClause.Count);
            return ExecuteScalar<int>(sql);
        }

        public static int Count(IDbTransaction trans, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(e, DbSchame.SqlClause.Count);
            return (int)DoExecuteScalar(sql, trans);
        }

        public static P Sum<P>(Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Sum);
            return ExecuteScalar<P>(sql);
        }

        public static P Sum<P>(IDbTransaction trans, Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Sum);
            return (P)DoExecuteScalar(sql, trans);
        }

        public static P Min<P>(Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Min);
            return ExecuteScalar<P>(sql);
        }

        public static P Min<P>(IDbTransaction trans, Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Min);
            return (P)DoExecuteScalar(sql, trans);
        }

        public static P Max<P>(Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Max);
            return ExecuteScalar<P>(sql);
        }

        public static P Max<P>(IDbTransaction trans, Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Max);
            var ret = DoExecuteScalar(sql, trans);
            if (ret == DBNull.Value) return default(P);
            return (P)ret;
        }

        public static P Average<P>(Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Average);
            return ExecuteScalar<P>(sql);
        }

        public static P Average<P>(IDbTransaction trans, Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Average);
            return (P)DoExecuteScalar(sql, trans);
        }

        public static P Value<P>(Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Column);
            return ExecuteScalar<P>(sql);
        }

        public static P Value<P>(IDbTransaction trans, Property<P> property, Expression<Func<T, bool>> e = null)
        {
            var sql = LinqHelper<T>.CreateExecuteScalarSql(property, e, DbSchame.SqlClause.Column);
            return (P)DoExecuteScalar(sql, trans);
        }

        public static P Value<P>(Property<P> property, Guid pkValue)
        {
            var sql = SqlStatement.CreateExecuteScalarSql(typeof(T), DbSchame.SqlClause.Column, property, string.Empty, null);
            sql.AddWhereClause(new KeyValuePair<IProperty, object>(ClassInfo.PrimaryKey, pkValue));
            return ExecuteScalar<P>(sql);
        }

        public static P Value<P>(IDbTransaction trans, Property<P> property, Guid pkValue)
        {
            var sql = SqlStatement.CreateExecuteScalarSql(typeof(T), DbSchame.SqlClause.Column, property, string.Empty, null);
            sql.AddWhereClause(new KeyValuePair<IProperty, object>(ClassInfo.PrimaryKey, pkValue));
            var ret = DoExecuteScalar(sql, trans);
            if (ret == DBNull.Value) return default(P);
            return (P)ret; 
        }
        #endregion

        #region 方法

        private static List<T> Query(SqlStatement sql)
        {
            return (List<T>)Inaction.Proxy.Query(typeof(T), sql);
        }

        internal static List<T> DoQuery(SqlStatement sql)
        {
            if (OnQueryingEvent != null)
            {
                bool skiped = false;
                var ret = OnQueryingEvent(sql, ref skiped);
                if (skiped) return ret;
            }
            var list = new List<T>();
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            var connection = DbProvider.Instance.Connection;
            try
            {
                using (var command = DbProvider.CreateCommand(connection, sql))
                {
                    IDataReader reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var t = new T();
                        t.Init(reader);
                        list.Add(t);
                        if (sql.RecordCount > 0 && list.Count >= sql.RecordCount) break;
                    }
                    reader.Close();
                    Util.Loger.WriteLog(sql);
                    if (OnQueryedEvent != null)
                    {
                        OnQueryedEvent(sql, list);
                    }
                    return list;
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                    connection.Close();
                watch.Stop();
                string log = "DoQuery_" + typeof(T).FullName + ":" + list.Count + "," + watch.ElapsedMilliseconds + "ms";
                Util.Loger.WriteLog(log);
            }
        }

        private static List<T> Query(SqlStatement sql, IDbTransaction trans)
        {
            return DoQuery(sql, trans);
        }

        internal static List<T> DoQuery(SqlStatement sql, IDbTransaction trans)
        {
            var list = new List<T>();
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            try
            {
                if (OnQueryingEvent != null)
                {
                    bool skiped = false;
                    var ret = OnQueryingEvent(sql, ref skiped);
                    if (skiped) return ret;
                }
                using (var command = DbProvider.CreateCommand(trans, sql))
                {
                    IDataReader reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var t = new T();
                        t.Init(reader);
                        list.Add(t);
                        if (sql.RecordCount > 0 && list.Count >= sql.RecordCount) break;
                    }
                    reader.Close();
                    Util.Loger.WriteLog(sql);
                    if (OnQueryedEvent != null)
                    {
                        OnQueryedEvent(sql, list);
                    }
                    return list;
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
            finally
            {
                watch.Stop();
                string log = "DoQuery_" + typeof(T).FullName + ":" + list.Count + "," + watch.ElapsedMilliseconds + "ms";
                Util.Loger.WriteLog(log);
            }
        }

        public static T DoSingle(SqlStatement sql)
        {
            T t = null;
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            var connection = DbProvider.Instance.Connection;
            try
            {
                using (var command = DbProvider.CreateCommand(connection, sql))
                {
                    IDataReader reader = command.ExecuteReader();
                    if (reader.Read())
                    {
                        t = new T();
                        t.Init(reader);
                    }
                    Util.Loger.WriteLog(sql);
                    return t;
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                    connection.Close();
                watch.Stop();
                string log = "DoQuerySingle_" + typeof(T).FullName + "," + watch.ElapsedMilliseconds + "ms";
                Util.Loger.WriteLog(log);
            }
        }

        public static T DoSingle(SqlStatement sql, IDbTransaction trans)
        {
            T t = null;
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            try
            {
                using (var command = DbProvider.CreateCommand(trans, sql))
                {
                    IDataReader reader = command.ExecuteReader();
                    if (reader.Read())
                    {
                        t = new T();
                        t.Init(reader);
                    }
                    Util.Loger.WriteLog(sql);
                    return t;
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
            finally
            {
                watch.Stop();
                string log = "DoQuerySingle_" + typeof(T).FullName + "," + watch.ElapsedMilliseconds + "ms";
                Util.Loger.WriteLog(log);
            }
        }

        private static P ExecuteScalar<P>(SqlStatement sql)
        {
            Func<SqlStatement, object> func = DoExecuteScalar;
            return RemotingMethod.Invoke<P>(func, sql);
        }

        private static object DoExecuteScalar(SqlStatement sql)
        {
            var connection = DbProvider.Instance.Connection;
            try
            {
                using (var command = DbProvider.CreateCommand(connection, sql))
                {
                    return command.ExecuteScalar();
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
            finally
            {
                if (connection.State == ConnectionState.Open)
                    connection.Close();
            }
        }

        private static object DoExecuteScalar(SqlStatement sql, IDbTransaction trans)
        {
            try
            {
                using (var command = DbProvider.CreateCommand(trans, sql))
                {
                    return command.ExecuteScalar();
                }
            }
            catch (Exception ex)
            {
                Util.Loger.WriteLog(sql);
                Util.Loger.WriteLog(ex);
                throw ex;
            }
        }
        #endregion

        #region OnQuery
        public delegate List<T> OnQueryingDelegate<T>(SqlStatement sql, ref bool skiped);
        public static event OnQueryingDelegate<T> OnQueryingEvent;

        public delegate void OnQueryedDelegate<T>(SqlStatement sql, List<T> list);
        public static event OnQueryedDelegate<T> OnQueryedEvent;
        #endregion
    }

    public class QueryEventArgs : EventArgs
    {
        bool applied;

        public bool Applied
        {
            get { return applied; }
            private set { applied = value; }
        }
    }
}